import time
import re
import time
import sys
from selenium import webdriver
from selenium.webdriver.common.by import By
from selenium.webdriver.chrome.service import Service
from selenium.webdriver.support.ui import WebDriverWait
from selenium.webdriver.support import expected_conditions as EC
from auto_exam_ser import upload_and_process_image
from auto_exam_ser import ident_img
from selenium.common.exceptions import TimeoutException # <--- 导入 TimeoutException
from selenium.webdriver.common.keys import Keys # <--- 1. 导入 Keys 模块
import pandas as pd
import concurrent.futures  # <--- 1. 导入并发库
from multiprocessing import Pool, cpu_count
# ==============================================================================
#                                  配置区
# ==============================================================================
YOUR_USERNAME = "19202534648"
YOUR_PASSWORD = "helizi123."
TARGET_URL = "https://www.mixlink.com/unify/login?redirect_uri=https%3A%2F%2Fwww.mixlink.com%2Fezvizmeeting%2F%23%2Fhome"
CHROME_DRIVER_PATH  = r"D:\drivers\chromedriver.exe"
MAX_LOGIN_ATTEMPTS = 10
CAPTCHA_ERROR_XPATH = "/html/body/div[2]/div/div/div/div/div/span[2]"
EXCEL_FILE_PATH = r"D:/accounts.xlsx"
ONLINE_DURATION_SECONDS = 3600  # 300 秒 = 5 分钟
# 设置最大同时运行的浏览器数量。请根据你的电脑性能调整此值！
# 对于普通笔记本电脑，建议从 3-5 开始测试。
MAX_CONCURRENT_BROWSERS = 20
COLUMN_NAMES = { "username": "username", "password": "password", "meeting_id": "meeting_id", "your_name": "your_name", "status": "status" }
SUCCESS_STATUS_FLAG = "已加入" # 你可以自定义成功的标记文本
MAX_LOGIN_ATTEMPTS_PER_ACCOUNT = 10
MAX_CONCURRENT_PROCESSES = 50
# ==============================================================================
def get_captcha_base64(driver, wait):
    """
    一个更健壮的辅助函数，用于获取验证码的 Base64 数据。
    """
    try:
        # --- 核心修正：更智能的等待 ---
        # 1. 定义验证码元素的定位器
        captcha_locator = (By.CLASS_NAME, "unify-login-captcha")
        
        # 2. 等待，直到该元素的 'style' 属性中包含 "base64," 字符串
        # 这确保了图片数据已经被 JavaScript 成功加载
        wait.until(
            EC.text_to_be_present_in_element_attribute(captcha_locator, "style", "base64,")
        )
        
        # 3. 此刻，我们可以安全地获取元素和它的 style 属性
        captcha_element = driver.find_element(*captcha_locator)
        style_attribute = captcha_element.get_attribute("style")
        
        # --- 调试打印 (非常重要) ---
        # print(f"  -> [调试] 获取到的 style 属性为: {style_attribute}")

        # --- 使用更具弹性的正则表达式 ---
        # 它可以匹配 jpg, png, jpeg，以及单引号或双引号
        match = re.search(r'url\s*\((["\']?)data:image/(jpg|png|jpeg);base64,(.+?)\1\)', style_attribute)
        
        if match:
            # group(3) 现在是我们需要的 base64 数据
            base64_data = match.group(3)
            print("  -> 成功提取到验证码 Base64 数据。")
            return base64_data
        else:
            print("  -> 错误：虽然 style 中有 base64, 但正则表达式未能匹配。")
            print(f"     Style 内容: {style_attribute}")
            return None

    except Exception as e:
        print(f"  -> 提取验证码时发生严重错误: {e}")
        return None

def process_single_account(account_info):
    driver = None
    # --- 从字典中解包出需要的信息 ---
    username = account_info[COLUMN_NAMES["username"]]
    process_name = f"进程 (账号: {username})"
    print(f"[{process_name}]：任务已启动。")
    username = str(account_info[COLUMN_NAMES["username"]])
    password = str(account_info[COLUMN_NAMES["password"]])
    meeting_id = str(account_info[COLUMN_NAMES["meeting_id"]])
    your_name = str(account_info[COLUMN_NAMES["your_name"]])
    # meeting_id = "53848311"
    # your_name = "孙灿成"
    print(f"\n--- 正在为账号 '{username}' 执行自动化 ---")
    try:
        print("正在启动 Chrome 浏览器...")
        driver = webdriver.Chrome()
        wait = WebDriverWait(driver, 15)
        
        print(f"正在打开登录页面: {TARGET_URL}")
        driver.get(TARGET_URL)
        driver.maximize_window()

        # --- 填写固定信息 ---
        wait.until(EC.element_to_be_clickable((By.ID, "login_name"))).send_keys(username)
        wait.until(EC.element_to_be_clickable((By.ID, "login_password"))).send_keys(password)
        print("用户名和密码已填写。")

        protocol_label = wait.until(EC.presence_of_element_located((By.CSS_SELECTOR, "div.unify-check-protocol.login label.ant-checkbox-wrapper")))
        driver.execute_script("arguments[0].click();", protocol_label)
        print("协议已勾选。")
        
        # --- 登录重试循环 ---
        for attempt in range(1, MAX_LOGIN_ATTEMPTS + 1):
            print(f"\n--- 正在进行第 {attempt} / {MAX_LOGIN_ATTEMPTS} 次登录尝试 ---")
            
            # --- 获取并输入验证码 ---
            base64_data = get_captcha_base64(driver, wait)
            
            user_captcha_input =  ident_img(base64_data)
            captcha_field = wait.until(EC.element_to_be_clickable((By.ID, "login_captcha")))
            for i in range(4):
                captcha_field.send_keys(Keys.BACK_SPACE)
            
            # 暂停程序，等待用户手动输入验证码
            #user_captcha_input = input("  -> 请在浏览器中查看验证码并在此处输入后按回车: ")
            captcha_field.send_keys(user_captcha_input)

            # --- 点击登录按钮 ---
            login_button = wait.until(EC.element_to_be_clickable((By.ID, "login")))
            login_button.click()
            print("  -> 登录按钮已点击，正在监控页面反馈...")

            # --- 监控错误提示 ---
            try:
                # 我们只等待很短的时间（例如5秒），因为错误提示应该会很快出现
                error_wait = WebDriverWait(driver, 5)
                error_element = error_wait.until(
                    EC.presence_of_element_located((By.XPATH, CAPTCHA_ERROR_XPATH))
                )
                
                # 如果代码能执行到这里，说明错误提示出现了
                print(f"  -> [失败] 检测到错误提示: '{error_element.text}'")
                
                if attempt < MAX_LOGIN_ATTEMPTS:
                    print("  -> 无法识别验证码浏览器将关闭")
                    # 点击验证码图片本身来刷新
                    #return {"username": username, "status": "FAILURE"}
                    captcha_image_to_refresh = wait.until(EC.element_to_be_clickable((By.CLASS_NAME, "unify-login-captcha")))
                    captcha_image_to_refresh.click()
                    time.sleep(3) # 等待新图片加载
                else:
                    print("  -> 已达到最大尝试次数，程序将退出。")
                    break # 跳出循环

            except TimeoutException:
                # 如果在5秒内没有找到错误提示，这是一个“好消息”！
                print("  -> [成功] 未检测到验证码错误提示，假定登录成功或正在跳转...")
                # ==================== 新增：点击登录按钮 ====================
                print("\n准备点击登录按钮...")
                try:
                    
                    #join_meeting_automation("03644701","测试")
                    
                    
                    # --- 3. 点击“加入会议”按钮 ---
                    print("正在查找并点击'加入会议'按钮...")
                    # 等待按钮容器变为可点击状态
                    join_meeting_button_container = wait.until(
                        EC.element_to_be_clickable((By.CLASS_NAME, "rtc-home-content-join"))
                    )
                    join_meeting_button_container.click()
                    print("  -> '加入会议'按钮已点击，等待弹窗出现...")

                    # --- 4. 在弹窗中填写会议ID ---
                    # 等待会议ID输入框在弹窗中可见
                    meeting_id_field = wait.until(
                        EC.visibility_of_element_located((By.ID, "name"))
                    )
                    meeting_id_field.clear() # 清空可能存在的默认值
                    meeting_id_field.send_keys(meeting_id)
                    print(f"  -> 已输入会议ID: {meeting_id}")
                    time.sleep(0.5)

                    # --- 5. 在弹窗中填写你的名字 ---
                    # 等待名字输入框可见
                    your_name_field = wait.until(
                        EC.visibility_of_element_located((By.ID, "account"))
                    )
                    for i in range(10):
                        your_name_field.send_keys(Keys.BACK_SPACE) # 清空默认的名字 "id63ek"
                    your_name_field.send_keys(your_name)
                    print(f"  -> 已输入名字: {your_name}")
                    time.sleep(0.5)

                    # --- 6. 点击“确认”按钮 ---
                    print("正在查找并点击'确认'按钮...")
                    # 使用 XPath 精准定位弹窗脚部的确认按钮
                    confirm_button_xpath = "//div[@class='ant-modal-footer']//button[contains(., '确 认')]"
                    confirm_button = wait.until(
                        EC.element_to_be_clickable((By.XPATH, confirm_button_xpath))
                    )
                    confirm_button.click()
                    print("  -> '确认'按钮已点击。")
                except Exception as e:
                    print(f"[!] 进入会议时发生错误: {e}")
                    return {"username": username, "status": "FAILURE"}
            # --- 完成 ---
                break # 成功跳出循环
        
        print("\n自动化流程结束。浏览器将保持打开 15 秒以便观察最终结果。")
        time.sleep(ONLINE_DURATION_SECONDS)
        return {"username": username, "status": "SUCCESS"}
    except Exception as e:
        print(f"\n程序执行过程中发生严重错误: {e}")
        return {"username": username, "status": "FAILURE"}
def automate_login_page1(username, password):
    driver = None
    try:
        service = Service(executable_path=CHROME_DRIVER_PATH)
        driver = webdriver.Chrome(service=service)
        print("正在启动 Chrome 浏览器...")
        driver = webdriver.Chrome()
        wait = WebDriverWait(driver, 15)
        
        print(f"正在打开登录页面: {TARGET_URL}")
        driver.get(TARGET_URL)
        driver.maximize_window()

        print(f"正在填写用户名: {username}")
        username_field = wait.until(EC.element_to_be_clickable((By.ID, "login_name")))
        username_field.send_keys(username)
        time.sleep(0.5)

        print(f"正在填写密码: {'*' * len(password)}")
        password_field = wait.until(EC.element_to_be_clickable((By.ID, "login_password")))
        password_field.send_keys(password)
        time.sleep(0.5)

        print("正在提取图形验证码 Base64 数据...")
        try:
            captcha_element = wait.until(
                EC.presence_of_element_located((By.CLASS_NAME, "unify-login-captcha"))
            )
            style_attribute = captcha_element.get_attribute("style")
            match = re.search(r'url\("data:image/jpg;base64,(.+?)"\)', style_attribute)
            if match:
                base64_data = match.group(1)
                print("成功提取到验证码 Base64 数据！(数据较长，仅显示前50个字符)")
                print(f"  -> {base64_data[:50]}...")
                result =  ident_img(base64_data)
                code_field = wait.until(EC.element_to_be_clickable((By.ID, "login_captcha")))
                code_field.send_keys(result)
                
            else:
                print("错误：未能从 style 属性中提取到 Base64 数据。")
        except Exception as e:
            print(f"提取验证码时发生错误: {e}")
        
        time.sleep(0.5)

        # ==================== 核心修正：勾选复选框 ====================
        print("正在勾选'我已阅读并同意'协议...")
        try:
            # 1. 定义一个极其精确的 CSS Selector
            protocol_label_selector = "div.unify-check-protocol.login label.ant-checkbox-wrapper"
            
            # 2. 等待这个 label 元素出现在页面上
            protocol_label = wait.until(
                EC.presence_of_element_located((By.CSS_SELECTOR, protocol_label_selector))
            )
            
            # 3. 使用 JavaScript 执行点击，这是最可靠的方式
            driver.execute_script("arguments[0].click();", protocol_label)
            
            # 4. (可选但推荐) 验证是否真的勾选上了
            time.sleep(0.5) # 等待一下状态更新
            checkbox_input = driver.find_element(By.ID, "tickprotocol")
            if checkbox_input.is_selected():
                print("  -> 验证成功：协议已勾选！")
                
                # ==================== 新增：点击登录按钮 ====================
                print("\n准备点击登录按钮...")
                try:
                    # 定位登录按钮，它的 ID 是 'login'
                    login_button = wait.until(
                        EC.element_to_be_clickable((By.ID, "login"))
                    )
                    # 执行点击
                    login_button.click()
                    print("  -> 登录按钮已点击。")
                    #join_meeting_automation("03644701","测试")
                    meeting_id = "79844481"
                    your_name = "测试"
                    
                    # --- 3. 点击“加入会议”按钮 ---
                    print("正在查找并点击'加入会议'按钮...")
                    # 等待按钮容器变为可点击状态
                    join_meeting_button_container = wait.until(
                        EC.element_to_be_clickable((By.CLASS_NAME, "rtc-home-content-join"))
                    )
                    join_meeting_button_container.click()
                    print("  -> '加入会议'按钮已点击，等待弹窗出现...")

                    # --- 4. 在弹窗中填写会议ID ---
                    # 等待会议ID输入框在弹窗中可见
                    meeting_id_field = wait.until(
                        EC.visibility_of_element_located((By.ID, "name"))
                    )
                    meeting_id_field.clear() # 清空可能存在的默认值
                    meeting_id_field.send_keys(meeting_id)
                    print(f"  -> 已输入会议ID: {meeting_id}")
                    time.sleep(0.5)

                    # --- 5. 在弹窗中填写你的名字 ---
                    # 等待名字输入框可见
                    your_name_field = wait.until(
                        EC.visibility_of_element_located((By.ID, "account"))
                    )
                    your_name_field.clear() # 清空默认的名字 "id63ek"
                    your_name_field.send_keys(your_name)
                    print(f"  -> 已输入名字: {your_name}")
                    time.sleep(0.5)

                    # --- 6. 点击“确认”按钮 ---
                    print("正在查找并点击'确认'按钮...")
                    # 使用 XPath 精准定位弹窗脚部的确认按钮
                    confirm_button_xpath = "//div[@class='ant-modal-footer']//button[contains(., '确 认')]"
                    confirm_button = wait.until(
                        EC.element_to_be_clickable((By.XPATH, confirm_button_xpath))
                    )
                    confirm_button.click()
                    print("  -> '确认'按钮已点击。")
                except Exception as e:
                    print(f"[!] 进入会议时发生错误: {e}")
            # --- 完成 ---
            print("\n所有操作已完成！浏览器将保持打开 10 秒以便观察结果。")
            time.sleep(10)

        except Exception as e:
            print(f"[!] 勾选协议时发生错误: {e}")
        # ==============================================================

        print("\n所有操作已完成！浏览器将保持打开 10 秒以便查看。")
        time.sleep(10)

    except Exception as e:
        print(f"\n程序执行过程中发生严重错误: {e}")

    
def join_meeting_automation(meeting_id,your_name):
    wait = None
    try:
    # --- 3. 点击“加入会议”按钮 ---
        print("正在查找并点击'加入会议'按钮...")
        # 等待按钮容器变为可点击状态
        join_meeting_button_container = wait.until(
            EC.element_to_be_clickable((By.CLASS_NAME, "rtc-home-content-join"))
        )
        join_meeting_button_container.click()
        print("  -> '加入会议'按钮已点击，等待弹窗出现...")

        # --- 4. 在弹窗中填写会议ID ---
        # 等待会议ID输入框在弹窗中可见
        meeting_id_field = wait.until(
            EC.visibility_of_element_located((By.ID, "name"))
        )
        meeting_id_field.clear() # 清空可能存在的默认值
        meeting_id_field.send_keys(meeting_id)
        print(f"  -> 已输入会议ID: {meeting_id}")
        time.sleep(0.5)

        # --- 5. 在弹窗中填写你的名字 ---
        # 等待名字输入框可见
        your_name_field = wait.until(
            EC.visibility_of_element_located((By.ID, "account"))
        )
        your_name_field.clear() # 清空默认的名字 "id63ek"
        your_name_field.send_keys(your_name)
        print(f"  -> 已输入名字: {your_name}")
        time.sleep(0.5)

        # --- 6. 点击“确认”按钮 ---
        print("正在查找并点击'确认'按钮...")
        # 使用 XPath 精准定位弹窗脚部的确认按钮
        confirm_button_xpath = "//div[@class='ant-modal-footer']//button[contains(., '确 认')]"
        confirm_button = wait.until(
            EC.element_to_be_clickable((By.XPATH, confirm_button_xpath))
        )
        confirm_button.click()
        print("  -> '确认'按钮已点击。")

        # --- 完成 ---
        print("\n所有操作已完成！浏览器将保持打开 10 秒以便观察结果。")
        time.sleep(10)

    except Exception as e:
        print(f"\n程序执行过程中发生严重错误: {e}")

def read_and_filter_accounts(file_path):
    """
    读取 Excel 文件，并筛选出所有未完成的任务。
    """
    try:
        print(f"[*] 正在从 '{file_path}' 文件中读取所有账号数据...")
        column_types = {k: str for k in COLUMN_NAMES.values()}
        # 使用 keep_default_na=False 确保空单元格是''而不是NaN
        df = pd.read_excel(file_path, dtype=column_types, keep_default_na=False)
        
        # 确保 status 列存在，如果不存在则创建它
        if COLUMN_NAMES["status"] not in df.columns:
            df[COLUMN_NAMES["status"]] = ""
            print(f"[*] '{COLUMN_NAMES['status']}' 列不存在，已自动创建。")
            
        total_accounts = len(df)
        print(f"[*] 共读取到 {total_accounts} 条记录。")

        # --- 核心筛选逻辑 ---
        # 筛选出 status 列不等于成功标记的行
        accounts_to_process = df[df[COLUMN_NAMES["status"]] != SUCCESS_STATUS_FLAG]
        
        print(f"[*] 筛选出 {len(accounts_to_process)} 个需要处理的账号。")
        # 返回完整的数据框和需要处理的索引列表
        return df, accounts_to_process.index
        
    except FileNotFoundError:
        print(f"[!] 错误：Excel 文件 '{file_path}' 未找到！")
        return None, []
    except Exception as e:
        print(f"[!] 读取 Excel 文件时发生错误: {e}")
        return None, []

def update_excel_status(df, index, status):
    """
    更新 DataFrame 中指定行的状态，并立即写回到 Excel 文件。
    """
    try:
        # 在内存中更新状态
        df.loc[index, COLUMN_NAMES["status"]] = status
        # 将整个 DataFrame 写回 Excel，index=False 表示不写入行索引
        df.to_excel(EXCEL_FILE_PATH, index=False)
        print(f"    -> Excel 状态已更新。")
    except Exception as e:
        print(f"[!] 更新 Excel 文件时发生严重错误: {e}")
def read_and_prepare_tasks(file_path):
    """
    读取 Excel 文件，自动创建 status 列，并筛选出所有未完成的任务。
    """
    try:
        print(f"[*] 正在从 '{file_path}' 读取所有账号数据...")
        column_types = {k: str for k in COLUMN_NAMES.values()}
        df = pd.read_excel(file_path, dtype=column_types, keep_default_na=False)
        
        if COLUMN_NAMES["status"] not in df.columns:
            df[COLUMN_NAMES["status"]] = ""
            print(f"[*] '{COLUMN_NAMES['status']}' 列不存在，已自动创建。")
            
        total_accounts = len(df)
        print(f"[*] 共读取到 {total_accounts} 条记录。")

        # 筛选出 status 列不等于成功标记的行
        filtered_df = df[df[COLUMN_NAMES["status"]] != SUCCESS_STATUS_FLAG]
        tasks_to_do = filtered_df.to_dict('records')
        
        print(f"[*] 筛选出 {len(tasks_to_do)} 个需要处理的账号。")
        # 返回完整的数据框和需要处理的任务列表
        return df, tasks_to_do
        
    except FileNotFoundError:
        print(f"[!] 错误：Excel 文件 '{file_path}' 未找到！")
        return None, []
    except Exception as e:
        print(f"[!] 读取 Excel 文件时发生错误: {e}")
        return None, []

def main():
    """
    主程序：使用进程池并发执行，并持久化状态到 Excel。
    """
    # 启动一个大的循环，直到所有任务完成
    while True:
        # 1. 读取并筛选当前需要处理的任务
        full_dataframe, tasks_to_do = read_and_prepare_tasks(EXCEL_FILE_PATH)
        
        if full_dataframe is None: break # 文件读取失败，退出
        if not tasks_to_do:
            print("\n=============================================")
            print("  所有账号均已成功加入，任务全部完成！")
            print("=============================================")
            break # 没有需要处理的任务，退出

        print(f"\n--- 开始新一轮并发处理，本轮有 {len(tasks_to_do)} 个任务，最大并发数: {MAX_CONCURRENT_PROCESSES} ---")

        # 2. 使用进程池并发处理本轮的所有任务
        with Pool(processes=MAX_CONCURRENT_PROCESSES) as pool:
            # pool.map 会阻塞，直到本轮所有任务都执行完毕
            results = pool.map(process_single_account, tasks_to_do)

        # 3. 在主进程中，根据返回的结果，集中更新 Excel 状态
        print("\n[*] 本轮并发处理结束，正在更新 Excel 状态...")
        updated = False
        for result in results:
            if result and result["status"] == "SUCCESS":
                # 找到对应用户名的行，并更新 status 列
                # full_dataframe['username'] == result['username'] 会返回一个布尔序列
                # .index[0] 获取第一个匹配项的索引
                try:
                    user_index = full_dataframe[full_dataframe[COLUMN_NAMES['username']] == result['username']].index[0]
                    full_dataframe.loc[user_index, COLUMN_NAMES['status']] = SUCCESS_STATUS_FLAG
                    updated = True
                except IndexError:
                    print(f"[!] 警告：在结果中找到用户 '{result['username']}'，但在原始Excel数据中未找到，无法更新状态。")
            else:
                user_index = full_dataframe[full_dataframe[COLUMN_NAMES['username']] == result['username']].index[0]
                full_dataframe.loc[user_index, COLUMN_NAMES['status']] = "失败"
                updated = True
        if updated:
            try:
                full_dataframe.to_excel(EXCEL_FILE_PATH, index=False)
                print("[*] Excel 文件状态已成功保存。")
            except Exception as e:
                print(f"[!] 保存 Excel 文件时发生严重错误: {e}")
        else:
            print("[*] 本轮没有成功完成的任务，无需更新 Excel。")
        
        print("\n--- 本轮处理结束，即将开始下一轮检查 ---")
        time.sleep(5) # 短暂间隔后，进入下一轮 while 循环

if __name__ == "__main__":
    main()
    time.sleep(1000)